/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package test;

import org.newdawn.slick.Animation;
import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.tiled.TiledMap;
import org.newdawn.slick.util.Log;

/**
 * An example to show scrolling around a tilemap smoothly. This seems to have caused confusion
 * a couple of times so here's "a" way to do it.
 *
 * @author kevin
 */
public class Scroller extends BasicGame {

    /**
     * Entry point to the scroller example
     * 
     * @param argv The argument passed on the command line (if any)
     */
    public static void main(String[] argv) {
        try {
            // create a new container for our example game. This container
            // just creates a normal native window for rendering OpenGL accelerated
            // elements to
            AppGameContainer container = new AppGameContainer(new Scroller(), 800, 600, false);
            container.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /** The size of the player sprite - used for finding the centre */
    private static final int PLAYER_SIZE = 32;
    /** The size of the tiles - used to determine the amount to draw */
    private static final int TILE_SIZE = 32;
    /** The speed the player moves at */
    private static final float WALK_SPEED = 0.003f;
    private static final float STEALTH_SPEED = 0.002f;
    /** The speed the player rotates at */
    private static final float TURN_SPEED = 0.2f;
    /** The width of the display in tiles */
    private int widthInTiles;
    /** The height of the display in tiles */
    private int heightInTiles;
    /** The offset from the centre of the screen to the top edge in tiles */
    private int topOffsetInTiles;
    /** The offset from the centre of the screen to the left edge in tiles */
    private int leftOffsetInTiles;
    /** The map that we're going to drive around */
    private TiledMap map;
    /** The animation representing the player's player */
    private Animation player;
    private Animation shots;
    boolean shooting = false;
    /** The angle the player is facing */
    float ang;
    /** The x component of the movement vector */
    float dirX;
    /** The y component of themovement vector */
    float dirY;
    /** The collision map indicating which tiles block movement - generated based on tile properties */
    private int[][] ground;
    private ScrollerSound sound;
    /** The player's x position in tiles */
    float playerX = 15;
    /** The player's y position in tiles */
    float playerY = 16;

    public static final int GRASS = 0;
    public static final int WOOD = 1;
    public static final int WATER = 2;
    
    public static final int IDLE = -1;
    public static final int WALK = 0;
    public static final int TURN = 1;
    public static final int STEALTH = 2;
    public static final int JUMP = 3;
    int movement = IDLE;
    
    
    private GameContainer container;
    
    /**
     * Scroller example
     */
    public Scroller() {
        super("Scroller");
        this.sound = new ScrollerSound(this);
    }

    /**
     * @see org.newdawn.slick.BasicGame#init(org.newdawn.slick.GameContainer)
     */
    public void init(GameContainer container) throws SlickException {
        this.container = container;
        
        // load the sprites and tiles, note that underneath the texture
        // will be shared between the sprite sheet and tilemap
        SpriteSheet sheet = new SpriteSheet("res/sprites.png", 32, 32);
        // load the tilemap created the TileD tool 
        map = new TiledMap("res/map.tmx");

        // build a collision map based on tile properties in the TileD map
        ground = new int[map.getWidth()][map.getHeight()];
        for (int x = 0; x < map.getWidth(); x++) {
            for (int y = 0; y < map.getHeight(); y++) {
                int tileID = map.getTileId(x, y, 0);
                String value = map.getTileProperty(tileID, "material", "");
                if ("wood".equals(value)) {
                    ground[x][y] = WOOD;
                } else if ("water".equals(value)) {
                    ground[x][y] = WATER;
                } else {
                    ground[x][y] = GRASS;
                }
            }
        }

        // caculate some layout values for rendering the tilemap. How many tiles
        // do we need to render to fill the screen in each dimension and how far is
        // it from the centre of the screen
        widthInTiles = container.getWidth() / TILE_SIZE;
        heightInTiles = container.getHeight() / TILE_SIZE;
        topOffsetInTiles = heightInTiles / 2;
        leftOffsetInTiles = widthInTiles / 2;

        // create the player sprite based on a set of sprites from the sheet loaded
        // above (player tracks moving)
        player = new Animation();
        for (int frame = 0; frame < 4; frame++) {
            player.addFrame(sheet.getSprite(frame, 1), 100);
        }
        player.setAutoUpdate(false);

        
        // create the player sprite based on a set of sprites from the sheet loaded
        // above (player tracks moving)
        shots = new Animation();
        for (int frame = 0; frame < 4; frame++) {
            shots.addFrame(sheet.getSprite(frame, 2), 50);
        }
        shots.setLooping(true);
        //shots.setAutoUpdate(false);

        
        // update the vector of movement based on the initial angle
        updateMovementVector();
        
        sound.init(container);

        Log.info("Window Dimensions in Tiles: " + widthInTiles + "x" + heightInTiles);
    }

    /**
     * @see org.newdawn.slick.BasicGame#update(org.newdawn.slick.GameContainer, int)
     */
    public void update(GameContainer container, int delta) throws SlickException {
        boolean stealth = container.getInput().isKeyDown(Input.KEY_LSHIFT);
        float moveSpeed = stealth ? STEALTH_SPEED : WALK_SPEED;
        movement = IDLE;
        player.setSpeed(stealth ? 0.50f : 1f);
        // check the controls, left/right adjust the rotation of the player, up/down 
        // move backwards and forwards
        if (container.getInput().isKeyDown(Input.KEY_A)) {
            ang -= delta * TURN_SPEED;
            updateMovementVector();
            movement = TURN;
        }
        if (container.getInput().isKeyDown(Input.KEY_D)) {
            ang += delta * TURN_SPEED;
            updateMovementVector();
            movement = TURN;
        }
        if (container.getInput().isKeyDown(Input.KEY_W)) {
            if (tryMove(dirX * delta * moveSpeed, dirY * delta * moveSpeed)) {
                // if we managed to move update the animation
                player.update(delta);
                movement = stealth ? STEALTH : WALK;
            }
        }
        if (container.getInput().isKeyDown(Input.KEY_S)) {
            if (tryMove(-dirX * delta * moveSpeed, -dirY * delta * moveSpeed)) {
                // if we managed to move update the animation
                player.update(delta);
                movement = stealth ? STEALTH : WALK;
            }
        }
        sound.update(container, delta);
    }

    /**
     * Check if a specific location of the player would leave it 
     * on a blocked tile
     * 
     * @param x The x coordinate of the player's location
     * @param y The y coordinate of the player's location
     * @return True if the location is blocked
     */
    private boolean blocked(float x, float y) {
        return groundAt(x, y) == WATER;
    }
    
    public int groundAt(float x, float y) {
        return ground[(int) x][(int) y];
    }

    /**
     * Try to move in the direction specified. If it's blocked, try sliding. If that
     * doesn't work just don't bother
     * 
     * @param x The amount on the X axis to move
     * @param y The amount on the Y axis to move
     * @return True if we managed to move
     */
    private boolean tryMove(float x, float y) {
        float newx = playerX + x;
        float newy = playerY + y;

        // first we try the real move, if that doesn't work
        // we try moving on just one of the axis (X and then Y) 
        // this allows us to slide against edges
        if (blocked(newx, newy)) {
            if (blocked(newx, playerY)) {
                if (blocked(playerX, newy)) {
                    // can't move at all!
                    return false;
                } else {
                    playerY = newy;
                    return true;
                }
            } else {
                playerX = newx;
                return true;
            }
        } else {
            playerX = newx;
            playerY = newy;
            return true;
        }
    }

    /**
     * Update the direction that will be moved in based on the
     * current angle of rotation
     */
    private void updateMovementVector() {
        dirX = (float) Math.sin(Math.toRadians(ang));
        dirY = (float) -Math.cos(Math.toRadians(ang));
    }

    /**
     * @see org.newdawn.slick.Game#render(org.newdawn.slick.GameContainer, org.newdawn.slick.Graphics)
     */
    public void render(GameContainer container, Graphics g) throws SlickException {
        // draw the appropriate section of the tilemap based on the centre (hence the -(player_SIZE/2)) of
        // the player
        int playerTileX = (int) playerX;
        int playerTileY = (int) playerY;

        // caculate the offset of the player from the edge of the tile. As the player moves around this
        // varies and this tells us how far to offset the tile based rendering to give the smooth
        // motion of scrolling
        int playerTileOffsetX = (int) ((playerTileX - playerX) * TILE_SIZE);
        int playerTileOffsetY = (int) ((playerTileY - playerY) * TILE_SIZE);
        
        // render the section of the map that should be visible. Notice the -1 and +3 which renders
        // a little extra map around the edge of the screen to cope with tiles scrolling on and off
        // the screen
        map.render(playerTileOffsetX - (PLAYER_SIZE / 2), playerTileOffsetY - (PLAYER_SIZE / 2),
                playerTileX - leftOffsetInTiles - 1,
                playerTileY - topOffsetInTiles - 1,
                widthInTiles + 3, heightInTiles + 3);

        // draw entities relative to the player that must appear in the centre of the screen
        g.translate(400 - (int) (playerX * 32), 300 - (int) (playerY * 32));
        
        sound.renderInView(container, g);
        g.setAntiAlias(true);
        drawPlayer(g, playerX, playerY, ang);
        // draw other entities here if there were any
        g.setAntiAlias(false);
        g.resetTransform();
        
        sound.render(container, g);
    }

    /**
     * Draw a single player to the game
     *  
     * @param g The graphics context on which we're drawing
     * @param xpos The x coordinate in tiles the player is at
     * @param ypos The y coordinate in tiles the player is at
     * @param rot The rotation of the player
     */
    public void drawPlayer(Graphics g, float xpos, float ypos, float rot) {
        // work out the centre of the player in rendering coordinates and then
        // spit onto the screen
        int cx = (int) (xpos * 32);
        int cy = (int) (ypos * 32);
        g.rotate(cx, cy, rot);
        player.draw(cx - 16, cy - 16);
        if (shooting) {
            shots.draw(cx - 15, cy - 24);
        }
        g.rotate(cx, cy, -rot);
        
        //shots.drawOval(cx-2* dirX, cy-2* dirY, 5, 5);
    }
    
    
    /**
     * @see org.newdawn.slick.BasicGame#keyPressed(int, char)
     */
    public void keyPressed(int key, char c) {
        if (key==Input.KEY_ESCAPE) {
            container.exit();
        }
        sound.keyPressed(key, c);
    }
}